import copy
import json
import sys
from collections import defaultdict
from typing import Dict

import click
import yaml
from flask import current_app as app
from flask.cli import with_appcontext

from app.common.rule import BlueNode, get_rule_tree
from app.models.roleModel import (
    METHOD_TO_FLAG,
    Permission,
    Role,
    RolePermission,
    UserRole,
)
from app.models.userModel import User
from app.role_control import BaseBlueControl, BaseRuleControl, PresetRoleChoices
from configs.settings import ALLOW_NO_LOGIN, RULE_WHITE_LIST, flask_config
from tools import db


def make_base_role_user(old_user_role_map: Dict):
    """生成基础角色和用户, 并建立基础用户角色关系"""
    project_id = list(flask_config.USER_TO_OPENSTACK.keys())[0]

    with db.session.begin(subtransactions=True):
        supper_user = db.session.query(User.id).filter_by(username="admin").first()
        if supper_user is None:
            supper_user = User(
                username="admin",
                account_name="管理员",
                password="abc123!@#",
                email="admin@163.com",
                project_id=project_id,
                is_superuser=1,
            )
            db.session.add(supper_user)

        rb_referee = db.session.query(User.id).filter_by(username="rb_referee").first()
        if rb_referee is None:
            rb_referee = User(
                username="rb_referee",
                account_name="裁判",
                password="123456",
                email="rb_referee@163.com",
                role_id=User.RoleChoices.rb_referee.value,
                project_id=project_id,
            )
            db.session.add(rb_referee)

        rb_pilot = db.session.query(User.id).filter_by(username="rb_pilot").first()
        if rb_pilot is None:
            rb_pilot = User(
                username="rb_pilot",
                account_name="导调",
                password="123456",
                email="rb_pilot@163.com",
                role_id=User.RoleChoices.rb_pilot.value,
                project_id=project_id,
            )
            db.session.add(rb_pilot)

        audience = db.session.query(User.id).filter_by(username="audience").first()
        if audience is None:
            audience = User(
                username="audience",
                account_name="观众",
                password="123456",
                email="audience@163.com",
                project_id=project_id,
            )
            db.session.add(audience)

        stud_list = []
        for val in ("stu1", "stu2"):
            stu = db.session.query(User.id).filter_by(username=val).first()
            if stu is None:
                _user = User(
                    username=val,
                    account_name=f"学生{val}",
                    password="123456",
                    email=f"{val}@163.com",
                    project_id=project_id,
                )
                db.session.add(_user)
                stud_list.append(_user)
            else:
                stud_list.append(stu)
        db.session.flush()

        # 清空系统内置角色与用户对应关系
        for item in UserRole.query.join(Role, Role.is_system == 1).all():
            db.session.delete(item)
        db.session.flush()
        Role.query.filter_by(is_system=True).delete()
        db.session.flush()

        teacher_role = Role(
            name=PresetRoleChoices.teacher.value,
            show_name="教师",
            description="教师角色",
            is_system=True,
            is_enabled=True,
        )
        student_role = Role(
            name=PresetRoleChoices.student.value,
            show_name="学生",
            description="学生角色",
            is_system=True,
            is_enabled=True,
        )
        rb_pilot_role = Role(
            name=PresetRoleChoices.rb_pilot.value,
            show_name="导调",
            description="导调角色",
            is_system=True,
            is_enabled=True,
        )
        rb_referee_role = Role(
            name=PresetRoleChoices.rb_referee.value,
            show_name="裁判",
            description="裁判角色",
            is_system=True,
            is_enabled=True,
        )
        audience_role = Role(
            name=PresetRoleChoices.audience.value,
            show_name="观众",
            description="观众角色",
            is_system=True,
            is_enabled=True,
        )
        admin_role = Role(
            name=PresetRoleChoices.admin.value,
            show_name="管理员",
            description="管理员角色",
            is_system=True,
            is_enabled=True,
        )
        db.session.add_all(
            [
                teacher_role,
                student_role,
                rb_pilot_role,
                rb_referee_role,
                audience_role,
                admin_role,
            ]
        )
        db.session.flush()

        user_for_role = []
        for user, role in zip(
            [supper_user, rb_referee, rb_pilot, audience, *stud_list],
            [
                teacher_role,
                rb_referee_role,
                rb_pilot_role,
                audience_role,
                *([student_role] * len(stud_list)),
            ],
        ):
            user_for_role.append(UserRole(user_id=user.id, role_id=role.id))
            old_user_role_map[user.id].remove(role.name) if old_user_role_map.get(
                user.id
            ) and role.name in old_user_role_map[user.id] else ...

        # 创建超级用户角色权限
        user_for_role.append(UserRole(user_id=supper_user.id, role_id=admin_role.id))
        old_user_role_map[supper_user.id].remove(
            admin_role.name
        ) if old_user_role_map.get(
            supper_user.id
        ) and admin_role.name in old_user_role_map[
            supper_user.id
        ] else ...

        db.session.add_all(user_for_role)
        db.session.flush()

    return (
        admin_role,
        teacher_role,
        rb_referee_role,
        rb_pilot_role,
        audience_role,
        student_role,
    )


def save_permission(node: BlueNode, parent_id: int = None):
    """递归保存权限资源信息"""

    with db.session.begin(subtransactions=True):

        if node.blue is not None:
            permission = Permission(
                name=node.name,
                type=Permission.TypeChoices.blueprint.value,
                parent_id=parent_id,
                is_menu=node.blue.role_control.is_menu,
            )
            db.session.add(permission)
            print(node.name)
            db.session.flush()
            node.blue.db_id = parent_id = permission.id

        if node.rules:
            for rule in node.rules:
                permission = Permission(
                    name=rule.endpoint,
                    type=Permission.TypeChoices.rule.value,
                    path=rule.rule,
                    parent_id=parent_id,
                    allow_no_login=1 if rule.rule in ALLOW_NO_LOGIN else 0,
                    is_menu=rule.role_control.is_menu if rule.role_control else 0,
                    is_white=1 if rule.rule in RULE_WHITE_LIST else 0,
                )
                print(rule.endpoint)
                db.session.add(permission)
                db.session.flush()
                rule.db_id = permission.id

        if node.children:
            for child in node.children:
                save_permission(child, parent_id=parent_id)


def bind_base_role_permission(
    role_map: Dict,
    node: BlueNode,
    parent_bp_allow_roles=None,
    parent_bp_allow_edit=True,
):
    """
    绑定基础角色权限信息
    :param role_map: 角色名：角色model对象
    :param node: 路由树节点
    :param parent_bp_allow_roles: 上层蓝图允许的角色叠加集合
    :param parent_bp_allow_edit: 父级蓝图允许进行权限编辑
    """
    with db.session.begin(subtransactions=True):

        parent_bp_allow_roles = (
            set() if parent_bp_allow_roles is None else parent_bp_allow_roles
        )

        # 处理当前蓝图
        if node.blue is not None:
            resources_id = node.blue.db_id
            res_control: BaseBlueControl = node.blue.role_control

            current_allow_roles: set = res_control.allow_roles & parent_bp_allow_roles
            parent_bp_allow_edit = bool(1 - res_control.lock)

            role_permission_list = [
                RolePermission(
                    role_id=role_map[role.value].id,
                    permission_id=resources_id,
                    allow_edit=parent_bp_allow_edit,
                )
                for role in current_allow_roles
            ]
            db.session.add_all(role_permission_list)
            db.session.flush()
        else:
            current_allow_roles = set(PresetRoleChoices)

        parent_bp_allow_roles = current_allow_roles
        if node.children:
            for child in node.children:
                bind_base_role_permission(
                    role_map,
                    child,
                    parent_bp_allow_roles=current_allow_roles,
                    parent_bp_allow_edit=parent_bp_allow_edit,
                )

        if node.rules is not None:
            for rule in node.rules:
                resources_id = rule.db_id
                res_control: BaseRuleControl = rule.role_control
                method_all_flag = sum(
                    {
                        METHOD_TO_FLAG[_m].value
                        for _m in rule.methods
                        if _m in METHOD_TO_FLAG
                    }
                )

                if res_control is False:
                    role_permission_list = [
                        RolePermission(
                            role_id=role_map[role.name].id,
                            permission_id=resources_id,
                            flag=method_all_flag,
                            allow_edit=False,
                        )
                        for role_name, role in role_map.items()
                        if role_name in parent_bp_allow_roles
                    ]
                    db.session.add_all(role_permission_list)
                    db.session.flush()
                else:
                    allow_roles = res_control.allow_roles
                    if parent_bp_allow_edit:
                        allow_edit = (
                            False
                            if res_control.lock or rule.rule in RULE_WHITE_LIST
                            else True
                        )
                    else:
                        allow_edit = False

                    if isinstance(allow_roles, dict):
                        role_permission = defaultdict(int)
                        rule_methods = set(copy.deepcopy(rule.methods)) & set(
                            METHOD_TO_FLAG.keys()
                        )
                        for method, roles in allow_roles.items():
                            rule_methods.remove(method)
                            for role in roles:
                                if role in parent_bp_allow_roles:
                                    role_permission[role] |= METHOD_TO_FLAG[method]

                        if rule_methods:
                            for role in parent_bp_allow_roles:
                                for method in rule_methods:
                                    role_permission[role] |= METHOD_TO_FLAG[method]

                        role_permission_list = [
                            RolePermission(
                                role_id=role_map[role_name].id,
                                permission_id=resources_id,
                                flag=flag,
                                allow_edit=allow_edit,
                            )
                            for role_name, flag in role_permission.items()
                        ]
                    else:
                        role_permission_list = [
                            RolePermission(
                                role_id=role_map[role.value].id,
                                permission_id=resources_id,
                                flag=method_all_flag,
                                allow_edit=allow_edit,
                            )
                            for role in res_control.allow_roles
                            if role in parent_bp_allow_roles  # 保留父级限制
                        ]
                    db.session.add_all(role_permission_list)
                    db.session.flush()


def bind_other_user_role():
    """检查是否存在其他用户，为其赋予学生权限"""

    other_user_ids = db.session.execute(
        """
        SELECT SQL_NO_CACHE api_user.id FROM api_user
        LEFT JOIN api_user_roles_map ON api_user.id = api_user_roles_map.user_id
        WHERE api_user_roles_map.user_id IS NULL
    """
    ).scalars()
    other_user_ids = list(other_user_ids)

    if other_user_ids:
        print("初始化其他用户权限", other_user_ids)
        stu_role_id = (
            Role.query.filter(Role.name == PresetRoleChoices.student).first().id
        )
        with db.session.begin(subtransactions=True):
            user_role_map = [
                UserRole(user_id=val, role_id=stu_role_id) for val in other_user_ids
            ]
            db.session.add_all(user_role_map)


@click.command("init_permission")
@click.option(
    "-s",
    "--style",
    default="json",
    type=click.Choice(["json", "yaml"]),
    help="路由层级输出格式: json or yaml",
)
@click.option(
    "-sw",
    "--skip_warning",
    default="n",
    type=click.Choice(["y", "n"]),
    help="跳过确认信息: yes(y) or no(n)",
)
@with_appcontext
def init_permission(style, skip_warning):
    """初始化权限"""

    if skip_warning != "y":
        inspect = input("该操作将清空所有权限及权限策略表，并重新初始化权限，非系统用户角色绑定信息将被清空，请确认是否继续？[y/n]: ")
        if inspect != "y":
            sys.exit(0)

    print(f"===>>> 即将执行: {init_permission.__doc__}\n")

    # 获取路由树
    node_tree = get_rule_tree()

    # 输出路由层级树
    print_style = {
        "json": lambda _tree: json.dumps(_tree.to_dict(), indent=4, ensure_ascii=False),
        "yaml": lambda _tree: yaml.dump(_tree.to_dict(), indent=4, allow_unicode=True),
    }
    print(print_style[style](node_tree))

    # 保留旧用户角色关系
    old_user_role_map = defaultdict(list)
    for item in Role.query.all():
        for val in item.users:
            old_user_role_map[val.id].append(item.name)

    try:
        with db.auto_commit():
            print(f"清空权限资源表：{Permission.__tablename__}")
            db.session.execute(
                f"use {flask_config.DATABASE}; TRUNCATE TABLE {Permission.__tablename__}"
            )
            print(f"清空角色权限关联表：{RolePermission.__tablename__}")
            db.session.execute(
                f"use {flask_config.DATABASE}; TRUNCATE TABLE {RolePermission.__tablename__}"
            )
    except Exception as e:
        app.logger.exception("清空权限相关表失败", exc_info=e)
        db.session.rollback()
        sys.exit()

    try:
        with db.auto_commit():
            save_permission(node_tree)
            role_list = make_base_role_user(old_user_role_map)
            bind_base_role_permission(
                {val.name: val for val in role_list},
                node_tree,
                parent_bp_allow_roles=set(),
            )
    except Exception as e:
        db.session.rollback()
        app.logger.exception("初始化角色权限失败，请检查后重试", exc_info=e)
        sys.exit()

    # 权限映射恢复
    try:
        with db.auto_commit():
            new_role_ids = {
                val.name: val.id
                for val in Role.query.filter(
                    Role.name.in_(
                        {v for val in old_user_role_map.values() for v in val}
                    )
                )
            }
            user_role_objs = []
            for user_id, role_name in old_user_role_map.items():
                for name in role_name:
                    user_role_objs.append(
                        UserRole(user_id=user_id, role_id=new_role_ids[name])
                    )
            db.session.add_all(user_role_objs)
    except Exception as e:
        db.session.rollback()
        app.logger.exception("权限恢复失败，请检查后重试", exc_info=e)
        sys.exit()

    print("权限初始化完成")
